#include "push_execute.h"

void (*ori_execute_ex)(zend_execute_data *execute_data) = nullptr;
void (*ori_execute_internal)(zend_execute_data *execute_data, zval *return_value) = nullptr;

// curl
extern void push_curl_init_handler(zend_execute_data *execute_data, zval *return_value, long execute_micro_time, string class_name, string function_name);
extern void push_curl_setopt_handler(zend_execute_data *execute_data, zval *return_value, long execute_micro_time, string class_name, string function_name);
extern void push_curl_setopt_array_handler(zend_execute_data *execute_data, zval *return_value, long execute_micro_time, string class_name, string function_name);
extern void push_curl_exec_handler(zend_execute_data *execute_data, zval *return_value, long execute_micro_time, string class_name, string function_name);
extern void push_curl_close_handler(zend_execute_data *execute_data, zval *return_value, long execute_micro_time, string class_name, string function_name);
// pdo
extern void push_pdo_handler(zend_execute_data *execute_data, zval *return_value, long execute_micro_time, string class_name, string function_name);
extern void push_pdo_stmt_handler(zend_execute_data *execute_data, zval *return_value, long execute_micro_time, string class_name, string function_name);
// mysqli
extern void push_mysqli_handler(zend_execute_data *execute_data, zval *return_value, long execute_micro_time, string class_name, string function_name);
extern void push_mysqli_obj_handler(zend_execute_data *execute_data, zval *return_value, long execute_micro_time, string class_name, string function_name);
extern void push_mysqli_stmt_handler(zend_execute_data *execute_data, zval *return_value, long execute_micro_time, string class_name, string function_name);
extern void push_mysqli_stmt_obj_handler(zend_execute_data *execute_data, zval *return_value, long execute_micro_time, string class_name, string function_name);
// phpredis
extern void push_phpredis_handler(zend_execute_data *execute_data, zval *return_value, long execute_micro_time, string class_name, string function_name);

typedef void (*internal_func)(zend_execute_data *, zval *, long, string, string);
map<string, internal_func> exec_internal_func = {
    {"null::curl_init", push_curl_init_handler},
    {"null::curl_setopt", push_curl_setopt_handler},
    {"null::curl_setopt_array", push_curl_setopt_array_handler},
    {"null::curl_exec", push_curl_exec_handler},
    {"null::curl_close", push_curl_close_handler},
    {"PDO::query", push_pdo_handler},
    {"PDO::exec", push_pdo_handler},
    {"PDO::prepare", push_pdo_handler},
    {"PDOStatement::execute", push_pdo_stmt_handler},
    {"null::mysqli_query", push_mysqli_handler},
    {"null::mysqli_prepare", push_mysqli_handler},
    {"mysqli::prepare", push_mysqli_obj_handler},
    {"null::mysqli_stmt_execute", push_mysqli_stmt_handler},
    {"mysqli_stmt::execute", push_mysqli_stmt_obj_handler},
    {"Redis::*", push_phpredis_handler},
};

// predis
extern void push_predis_handler(zend_execute_data *execute_data, long execute_micro_time, string class_name, string function_name);

typedef void (*ex_func)(zend_execute_data *, long, string, string);
map<string, ex_func> exec_ex_func = {
    {"Predis\\Client::__call", push_predis_handler},
};

void push_execute_internal(zend_execute_data *execute_data, zval *return_value)
{
    struct timeval tv;
    long execute_micro_time = 0, start_micro_time, end_micro_time;
    gettimeofday(&tv, NULL);
    start_micro_time = tv.tv_sec * 1000000 + tv.tv_usec;

    if (ori_execute_internal)
    {
        ori_execute_internal(execute_data, return_value);
    }
    else
    {
        execute_internal(execute_data, return_value);
    }

    gettimeofday(&tv, NULL);
    end_micro_time = tv.tv_sec * 1000000 + tv.tv_usec;
    execute_micro_time = end_micro_time - start_micro_time;

    // 获取类名 和 方法名
    zend_function *func = execute_data->func;
    int is_class = func->common.scope != nullptr && func->common.scope->name != nullptr;
    char *class_name = is_class ? ZSTR_VAL(func->common.scope->name) : nullptr;
    char *function_name = func->common.function_name != nullptr ? ZSTR_VAL(func->common.function_name) : nullptr;

    // php_printf("call internal:%s->%s\n", class_name, function_name);

    string cn = class_name ? class_name : "null";
    string fn = function_name ? function_name : "null";
    string cf = cn + "::" + fn;
    string cf_all = cn + "::*";

    if (exec_internal_func.count(cf) > 0)
    {
        exec_internal_func[cf](execute_data, return_value, execute_micro_time, cn, fn);
    }
    else if (exec_internal_func.count(cf_all) > 0)
    {
        exec_internal_func[cf_all](execute_data, return_value, execute_micro_time, cn, fn);
    }

    // long push_micro_time, diff_micro_time;
    // gettimeofday(&tv, NULL);
    // push_micro_time = tv.tv_sec * 1000000 + tv.tv_usec;
    // diff_micro_time = push_micro_time - end_micro_time;
    // php_printf("execute push function[%s->%s] time: %ld\n", class_name, function_name, diff_micro_time);
}

void push_execute_ex(zend_execute_data *execute_data)
{
    struct timeval tv;
    long execute_micro_time = 0, start_micro_time, end_micro_time;
    gettimeofday(&tv, NULL);
    start_micro_time = tv.tv_sec * 1000000 + tv.tv_usec;

    ori_execute_ex(execute_data);

    gettimeofday(&tv, NULL);
    end_micro_time = tv.tv_sec * 1000000 + tv.tv_usec;
    execute_micro_time = end_micro_time - start_micro_time;

    // 获取类名 和 方法名
    zend_function *func = execute_data->func;
    int is_class = func->common.scope != nullptr && func->common.scope->name != nullptr;
    char *class_name = is_class ? ZSTR_VAL(func->common.scope->name) : nullptr;
    char *function_name = func->common.function_name != nullptr ? ZSTR_VAL(func->common.function_name) : nullptr;

    // php_printf("call ex:%s->%s\n", class_name, function_name);

    string cn = class_name ? class_name : "null";
    string fn = function_name ? function_name : "null";
    string cf = cn + "::" + fn;
    string cf_all = cn + "::*";

    if (exec_ex_func.count(cf) > 0)
    {
        exec_ex_func[cf](execute_data, execute_micro_time, cn, fn);
    }
    else if (exec_ex_func.count(cf_all) > 0)
    {
        exec_ex_func[cf_all](execute_data, execute_micro_time, cn, fn);
    }
}

// void push_thread(string addr, string query, string error, long execute_micro_time, string bucket)
// {
//     client *pushgateway_client = client::newInstance();
//     scaler *sc_total = new scaler;
//     scaler *sc_second = new scaler;

//     if (error == "")
//     {
//         sc_total->setIndex(INDEX_SQL_USEGE_TOTAL);
//         sc_total->addItem(ITEM_SQL_ADDR, addr);
//         sc_total->addItem(ITEM_SQL_NAME, query);
//         pushgateway_client->setCounter(sc_total);
//     }
//     else
//     {
//         sc_total->setIndex(INDEX_SQL_USEGE_ERROR_TOTAL);
//         sc_total->addItem(ITEM_SQL_ADDR, addr);
//         sc_total->addItem(ITEM_SQL_NAME, query);
//         sc_total->addItem(ITEM_SQL_ERROR, error);
//         pushgateway_client->setCounter(sc_total);
//     }

//     sc_second->setIndex(INDEX_SQL_USEGE_DURATION_S);
//     sc_second->addItem(ITEM_SQL_ADDR, addr);
//     sc_second->addItem(ITEM_SQL_NAME, query);
//     pushgateway_client->setBucket(sc_second, execute_micro_time * pow(0.1, PUSH_HISTOGRAM_DECIAML), bucket);
// }

void push_db_data(string addr, string query, string error, long execute_micro_time)
{
    string bucket = PUSHGATEWAY_G(bucket);
    // thread th(push_thread, addr, query, error, execute_micro_time, bucket);
    // th.detach();

    client *pushgateway_client = client::newInstance();
    scaler *sc_total = new scaler;
    scaler *sc_second = new scaler;

    if (error == "")
    {
        sc_total->setIndex(INDEX_SQL_USEGE_TOTAL);
        sc_total->addItem(ITEM_SQL_ADDR, addr);
        sc_total->addItem(ITEM_SQL_NAME, query);
        pushgateway_client->setCounter(sc_total);
    }
    else
    {
        sc_total->setIndex(INDEX_SQL_USEGE_ERROR_TOTAL);
        sc_total->addItem(ITEM_SQL_ADDR, addr);
        sc_total->addItem(ITEM_SQL_NAME, query);
        sc_total->addItem(ITEM_SQL_ERROR, error);
        pushgateway_client->setCounter(sc_total);
    }

    sc_second->setIndex(INDEX_SQL_USEGE_DURATION_S);
    sc_second->addItem(ITEM_SQL_ADDR, addr);
    sc_second->addItem(ITEM_SQL_NAME, query);
    pushgateway_client->setBucket(sc_second, execute_micro_time * pow(0.1, PUSH_HISTOGRAM_DECIAML), bucket);
}